🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / app / src / main / java / com / geeksville / mesh / database / dao / NodeInfoDao.kt
Displaying Raw • Download
app/src/main/java/com/geeksville/mesh/database/dao/NodeInfoDao.kt ui_message_list (be3f882f) Text, 8.61 KB
T8b949e/*
* Copyright (c) 2025 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787com.geeksville.mesh.database.dao
Tff7b72import T7ee787androidx.room.Dao
Tff7b72import T7ee787androidx.room.Insert
Tff7b72import T7ee787androidx.room.MapColumn
Tff7b72import T7ee787androidx.room.OnConflictStrategy
Tff7b72import T7ee787androidx.room.Query
Tff7b72import T7ee787androidx.room.Transaction
Tff7b72import T7ee787androidx.room.Upsert
Tff7b72import T7ee787com.geeksville.mesh.database.entity.MetadataEntity
Tff7b72import T7ee787com.geeksville.mesh.database.entity.MyNodeEntity
Tff7b72import T7ee787com.geeksville.mesh.database.entity.NodeEntity
Tff7b72import T7ee787com.geeksville.mesh.database.entity.NodeWithRelations
Tff7b72import T7ee787com.google.protobuf.ByteString
Tff7b72import T7ee787kotlinx.coroutines.flow.Flow
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooManyFunctionsTa5d6ff"Tb4b4b4)
Tf0883e@Dao
Tff7b72interface T56d364NodeInfoDao Tb4b4b4{
T8b949e/**
* Verifies a [NodeEntity] before an upsert operation. It handles populating the publicKey for lazy migration,
* checks for public key conflicts with new nodes, and manages updates to existing nodes, particularly in cases of
* public key mismatches to prevent potential impersonation or data corruption.
*
* @param incomingNode The node entity to be verified.
* @return A [NodeEntity] that is safe to upsert, or null if the upsert should be aborted (e.g., due to an
* impersonation attempt, though this logic is currently commented out).
*/
Tff7b72private Tff7b72fun Td2a8ffgetVerifiedNodeForUpsertTb4b4b4(Te6edf3incomingNodeTb4b4b4: Te6edf3NodeEntityTb4b4b4)Tb4b4b4: Te6edf3NodeEntity Tb4b4b4{
T8b949e// Populate the NodeEntity.publicKey field from the User.publicKey for consistency
T8b949e// and to support lazy migration.
Te6edf3incomingNodeTb4b4b4.Te6edf3publicKey Tff7b72= Te6edf3incomingNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3publicKey
Tff7b72val Te6edf3existingNodeEntity Tff7b72= Te6edf3getNodeByNumTb4b4b4(Te6edf3incomingNodeTb4b4b4.Te6edf3numTb4b4b4)Tff7b72?.Te6edf3node
Tff7b72return Tff7b72if Tb4b4b4(Te6edf3existingNodeEntity Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3handleNewNodeUpsertValidationTb4b4b4(Te6edf3incomingNodeTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3handleExistingNodeUpsertValidationTb4b4b4(Te6edf3existingNodeEntityTb4b4b4, Te6edf3incomingNodeTb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e/** Validates a new node before it is inserted into the database. */
Tff7b72private Tff7b72fun Td2a8ffhandleNewNodeUpsertValidationTb4b4b4(Te6edf3newNodeTb4b4b4: Te6edf3NodeEntityTb4b4b4)Tb4b4b4: Te6edf3NodeEntity Tb4b4b4{
T8b949e// Check if the new node's public key (if present and not empty)
T8b949e// is already claimed by another existing node.
Tff7b72if Tb4b4b4(Te6edf3newNodeTb4b4b4.Te6edf3publicKeyTff7b72?.Te6edf3isEmpty Tff7b72=Tff7b72= Tff7b72falseTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeWithSamePK Tff7b72= Te6edf3findNodeByPublicKeyTb4b4b4(Te6edf3newNodeTb4b4b4.Te6edf3publicKeyTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3nodeWithSamePK Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3nodeWithSamePKTb4b4b4.Te6edf3num Tff7b72!Tff7b72= Te6edf3newNodeTb4b4b4.Te6edf3numTb4b4b4) Tb4b4b4{
T8b949e// This is a potential impersonation attempt.
Tff7b72return Te6edf3nodeWithSamePK
Tb4b4b4}
Tb4b4b4}
T8b949e// If no conflicting public key is found, or if the impersonation check is not active,
T8b949e// the new node is considered safe to add.
Tff7b72return Te6edf3newNode
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffhandleExistingNodeUpsertValidationTb4b4b4(Te6edf3existingNodeTb4b4b4: Te6edf3NodeEntityTb4b4b4, Te6edf3incomingNodeTb4b4b4: Te6edf3NodeEntityTb4b4b4)Tb4b4b4: Te6edf3NodeEntity Tb4b4b4{
T8b949e// A public key is considered matching if the incoming key equals the existing key,
T8b949e// OR if the existing key is empty (allowing a new key to be set or an update to proceed).
Tff7b72val Te6edf3isPublicKeyMatchingOrExistingIsEmpty Tff7b72=
Te6edf3existingNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3publicKey Tff7b72=Tff7b72= Te6edf3incomingNodeTb4b4b4.Te6edf3publicKey Tff7b72|Tff7b72| Te6edf3existingNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3publicKeyTb4b4b4.Te6edf3isEmpty
Tff7b72return Tff7b72if Tb4b4b4(Te6edf3isPublicKeyMatchingOrExistingIsEmptyTb4b4b4) Tb4b4b4{
T8b949e// Keys match or existing key was empty: trust the incoming node data completely.
T8b949e// This allows for legitimate updates to user info and other fields.
Tff7b72val Te6edf3resolvedNotes Tff7b72= Tff7b72if Tb4b4b4(Te6edf3incomingNodeTb4b4b4.Te6edf3notesTb4b4b4.Te6edf3isBlankTb4b4b4(Tb4b4b4)Tb4b4b4) Te6edf3existingNodeTb4b4b4.Te6edf3notes Tff7b72else Te6edf3incomingNodeTb4b4b4.Te6edf3notes
Te6edf3incomingNodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3notes Tff7b72= Te6edf3resolvedNotesTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3existingNodeTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3lastHeard Tff7b72= Te6edf3incomingNodeTb4b4b4.Te6edf3lastHeardTb4b4b4,
Te6edf3snr Tff7b72= Te6edf3incomingNodeTb4b4b4.Te6edf3snrTb4b4b4,
Te6edf3position Tff7b72= Te6edf3incomingNodeTb4b4b4.Te6edf3positionTb4b4b4,
T8b949e// Preserve the existing user object, but update its internal public key to EMPTY
T8b949e// to reflect the conflict state.
Te6edf3user Tff7b72= Te6edf3existingNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3toBuilderTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3setPublicKeyTb4b4b4(Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4.Te6edf3buildTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3publicKey Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4,
Te6edf3notes Tff7b72= Te6edf3existingNodeTb4b4b4.Te6edf3notesTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM my_nodeTa5d6ff"Tb4b4b4)
Tff7b72fun Td2a8ffgetMyNodeInfoTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<Te6edf3MyNodeEntity?Tff7b72>
Tf0883e@InsertTb4b4b4(Te6edf3onConflict Tff7b72= Te6edf3OnConflictStrategyTb4b4b4.Te6edf3REPLACETb4b4b4)
Tff7b72fun Td2a8ffsetMyNodeInfoTb4b4b4(Te6edf3myInfoTb4b4b4: Te6edf3MyNodeEntityTb4b4b4)
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM my_nodeTa5d6ff"Tb4b4b4)
Tff7b72fun Td2a8ffclearMyNodeInfoTb4b4b4(Tb4b4b4)
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM nodes
ORDER BY CASE
WHEN num = (SELECT myNodeNum FROM my_node LIMIT 1) THEN 0
ELSE 1
END,
last_heard DESC
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tf0883e@Transaction
Tff7b72fun Td2a8ffnodeDBbyNumTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<
Te6edf3MapTff7b72<
Tf0883e@MapColumnTb4b4b4(Te6edf3columnName Tff7b72= Ta5d6ff"Ta5d6ffnumTa5d6ff"Tb4b4b4)
Tffa657IntTb4b4b4,
Te6edf3NodeWithRelationsTb4b4b4,
Tff7b72>Tb4b4b4,
Tff7b72>
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff WITH OurNode AS (
SELECT latitude, longitude
FROM nodes
WHERE num = (SELECT myNodeNum FROM my_node LIMIT 1)
)
SELECT * FROM nodes
WHERE (:includeUnknown = 1 OR short_name IS NOT NULL)
AND (:filter = ''
OR (long_name LIKE '%' || :filter || '%'
OR short_name LIKE '%' || :filter || '%'
OR printf('!%08x', CASE WHEN num < 0 THEN num + 4294967296 ELSE num END) LIKE '%' || :filter || '%'
OR CAST(CASE WHEN num < 0 THEN num + 4294967296 ELSE num END AS TEXT) LIKE '%' || :filter || '%'))
AND (:lastHeardMin = -1 OR last_heard >= :lastHeardMin)
AND (:hopsAwayMax = -1 OR (hops_away <= :hopsAwayMax AND hops_away >= 0) OR num = (SELECT myNodeNum FROM my_node LIMIT 1))
ORDER BY CASE
WHEN num = (SELECT myNodeNum FROM my_node LIMIT 1) THEN 0
ELSE 1
END,
CASE
WHEN :sort = 'last_heard' THEN last_heard * -1
WHEN :sort = 'alpha' THEN UPPER(long_name)
WHEN :sort = 'distance' THEN
CASE
WHEN latitude IS NULL OR longitude IS NULL OR
(latitude = 0.0 AND longitude = 0.0) THEN 999999999
ELSE
(latitude - (SELECT latitude FROM OurNode)) *
(latitude - (SELECT latitude FROM OurNode)) +
(longitude - (SELECT longitude FROM OurNode)) *
(longitude - (SELECT longitude FROM OurNode))
END
WHEN :sort = 'hops_away' THEN
CASE
WHEN hops_away = -1 THEN 999999999
ELSE hops_away
END
WHEN :sort = 'channel' THEN channel
WHEN :sort = 'via_mqtt' THEN via_mqtt
WHEN :sort = 'via_favorite' THEN is_favorite * -1
ELSE 0
END ASC,
last_heard DESC
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tf0883e@Transaction
Tff7b72fun Td2a8ffgetNodesTb4b4b4(
Te6edf3sortTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3filterTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3includeUnknownTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3hopsAwayMaxTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3lastHeardMinTb4b4b4: Tffa657IntTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<Te6edf3ListTff7b72<Te6edf3NodeWithRelationsTff7b72>Tff7b72>
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM nodesTa5d6ff"Tb4b4b4)
Tff7b72fun Td2a8ffclearNodeInfoTb4b4b4(Tb4b4b4)
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM nodes WHERE num=:numTa5d6ff"Tb4b4b4)
Tff7b72fun Td2a8ffdeleteNodeTb4b4b4(Te6edf3numTb4b4b4: Tffa657IntTb4b4b4)
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM nodes WHERE num IN (:nodeNums)Ta5d6ff"Tb4b4b4)
Tff7b72fun Td2a8ffdeleteNodesTb4b4b4(Te6edf3nodeNumsTb4b4b4: Te6edf3ListTff7b72<Tffa657IntTff7b72>Tb4b4b4)
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM nodes WHERE last_heard < :lastHeardTa5d6ff"Tb4b4b4)
Tff7b72fun Td2a8ffgetNodesOlderThanTb4b4b4(Te6edf3lastHeardTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3NodeEntityTff7b72>
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM nodes WHERE short_name IS NULLTa5d6ff"Tb4b4b4)
Tff7b72fun Td2a8ffgetUnknownNodesTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3NodeEntityTff7b72>
Tf0883e@Upsert Tff7b72fun Td2a8ffupsertTb4b4b4(Te6edf3metaTb4b4b4: Te6edf3MetadataEntityTb4b4b4)
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM metadata WHERE num=:numTa5d6ff"Tb4b4b4)
Tff7b72fun Td2a8ffdeleteMetadataTb4b4b4(Te6edf3numTb4b4b4: Tffa657IntTb4b4b4)
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM nodes WHERE num=:numTa5d6ff"Tb4b4b4)
Tf0883e@Transaction
Tff7b72fun Td2a8ffgetNodeByNumTb4b4b4(Te6edf3numTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3NodeWithRelations?
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM nodes WHERE public_key = :publicKey LIMIT 1Ta5d6ff"Tb4b4b4)
Tff7b72fun Td2a8fffindNodeByPublicKeyTb4b4b4(Te6edf3publicKeyTb4b4b4: Te6edf3ByteString?Tb4b4b4)Tb4b4b4: Te6edf3NodeEntity?
Tf0883e@Upsert Tff7b72fun Td2a8ffdoUpsertTb4b4b4(Te6edf3nodeTb4b4b4: Te6edf3NodeEntityTb4b4b4)
Tff7b72fun Td2a8ffupsertTb4b4b4(Te6edf3nodeTb4b4b4: Te6edf3NodeEntityTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3verifiedNode Tff7b72= Te6edf3getVerifiedNodeForUpsertTb4b4b4(Te6edf3nodeTb4b4b4)
Te6edf3doUpsertTb4b4b4(Te6edf3verifiedNodeTb4b4b4)
Tb4b4b4}
Tf0883e@InsertTb4b4b4(Te6edf3onConflict Tff7b72= Te6edf3OnConflictStrategyTb4b4b4.Te6edf3REPLACETb4b4b4)
Tff7b72fun Td2a8ffputAllTb4b4b4(Te6edf3nodesTb4b4b4: Te6edf3ListTff7b72<Te6edf3NodeEntityTff7b72>Tb4b4b4)
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffUPDATE nodes SET notes = :notes WHERE num = :numTa5d6ff"Tb4b4b4)
Tff7b72fun Td2a8ffsetNodeNotesTb4b4b4(Te6edf3numTb4b4b4: Tffa657IntTb4b4b4, Te6edf3notesTb4b4b4: Tffa657StringTb4b4b4)
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.13s